# coding: UTF-8 -*-

import xml.dom.minidom as dom

class PickleMeToXML(object): pass

# helper function

# TODO: remove this method
def getType(obj):
    """ generates string representation of class of obj 
        discarding decoration """
    #return str(obj.__class__).split("'")[1].split(".")[-1]
    #return str(obj.__class__).split("'")[1]
    return "%s.%s" % (type(obj).__module__, type(obj).__name__)

_easyToPickle = [ "int", "float", "str", "unicode" ]

_isCallable = lambda o: hasattr(o, "__call__")

# 
#   pickling 
# 

def _pickleDictItems(root, node, fabric, isCDATA=False):
    for key, value in root.items():
        tempnode = fabric.createElement("item")
        tempnode.appendChild(_xmlpickle(key, fabric, "key"))
        tempnode.appendChild(_xmlpickle(value, fabric, "value", isCDATA))
        node.appendChild(tempnode)

def _pickleListItems(root, node, fabric):
    for idx, obj in enumerate(root):
        tempnode = _xmlpickle(obj, fabric, "item")
        tempnode.attributes["index"] = str(idx)
        node.appendChild(tempnode)

_pickleTupleItems = _pickleListItems

def pickle(obj):
    fabric=dom.Document()
    node = _xmlpickle(obj, fabric)
    return node.toxml()

def _xmlpickle(root, fabric, elementName="root", isCDATA=False):

    node = fabric.createElement(elementName)
    typeStr = type(root).__name__
    node.attributes["name"] = typeStr
    #typeStr = getType(root)
    #node.attributes["type"]=typeStr

    if isinstance(root, PickleMeToXML):
        node = _pickleObjectWithAttributes(node, root, fabric, elementName)
    elif typeStr in _easyToPickle:
        val = unicode(root).encode("utf-8")
        if isCDATA:
            node.appendChild(fabric.createCDATASection(val))
        else:
            node.appendChild(fabric.createTextNode(val))
    elif root is None:
        return node
    elif isinstance(root, dict):
        _pickleDictItems(root, node, fabric, isCDATA)
    elif isinstance(root, list):
        _pickleListItems(root, node, fabric)
    elif isinstance(root, tuple):
        _pickleTupleItems(root, node, fabric)
    else:
        # fallback handler
        node.appendChild(fabric.createTextNode(repr(root)))
    return node

def _pickleObjectWithAttributes(node, root, fabric, elementName):

    # _xmlpickle all members or just a subset ??? 
    if hasattr(root, "__pickle_to_xml__"):
        attributesToPickle = root.__pickle_to_xml__
    else:
        # avoid members which are python internal
        attributesToPickle = [ name for name in dir(root) if not name.startswith("__") ]

    # construct the list of CData variables
    if hasattr(root, "__pickle_as_cdata__"):
        cdata_attributes = root.__pickle_as_cdata__
    else:
        cdata_attributes = []

    for name in attributesToPickle: 
        obj = getattr(root, name)

        # do not _xmlpickle member functions
        if _isCallable(obj): continue

        # is there some special encoding method ??
        if hasattr(root, "_xml_encode_%s" % name):
            value = getattr(root, "_xml_encode_%s" % name)()
            node.appendChild(fabric.createTextNode(value))
        elif name in cdata_attributes:
            node.appendChild(_xmlpickle(obj, fabric, name, True))
        else:
            node.appendChild(_xmlpickle(obj, fabric, name))
    node.attributes["module"] = type(root).__module__
    return node

#
#   unpickling 
#

# helper functions

def _getElementChilds(node, doLower = 1):
    """ returns list of (tagname, element) for all element childs of node """

    dolow = doLower and (lambda x:x.lower()) or (lambda x:x)
    return [ (dolow(no.tagName), no) for no in node.childNodes if no.nodeType != no.TEXT_NODE ]

def _getText(nodelist):
    """ returns collected and stripped text of textnodes among nodes in nodelist """
    rc = ""
    for node in nodelist:
        if node.nodeType in [node.TEXT_NODE, node.CDATA_SECTION_NODE]:
            rc = rc + node.data
    return rc.strip()

def unpickle(xml):
    fabric = dom.parseString(xml)
    node = fabric.getElementsByTagName("root")[0]
    return _xmlunpickle(node)
    
# main _xmlunpickle function
def _xmlunpickle(node):

    typeName= node.attributes["name"].value

    if typeName in _easyToPickle: 
        initValue = _getText(node.childNodes)
        # TODO: remove this
        #value = initValue.decode("utf-8")
        value = eval("%s(%r)" % (typeName, initValue))
        return value
    elif typeName.lower()=="bool":
        initValue = _getText(node.childNodes)
        value = eval("%s(%s)" % (typeName, initValue))
        return value 
    elif typeName.lower()=="nonetype":
        return None
    elif typeName=="tuple":
        return _unpickleTuple(node)
    elif typeName=="list":
        return _unpickleList(node)
    elif typeName=="dict":
        return _unpickleDict(node)
    else:
        moduleName = node.attributes["module"].value
        exec "import %s" % moduleName
        obj = eval("object.__new__(%s.%s)" % (moduleName ,typeName))
        for name, element in _getElementChilds(node):
            setattr(obj, name, _xmlunpickle(element))
        return obj
        
class XMLUnpicklingException(Exception): pass

def _unpickleList(node):
    li = []
    # collect entries, you can not assume that the
    # members of the list appear in the right order !
    for name, element in _getElementChilds(node):
        if name != "item":
            raise XMLUnpicklingException()
        idx = int(element.attributes["index"].value)
        obj = _xmlunpickle(element)
        li.append((idx, obj))

    # rebuild list with right order
    li.sort()
    return [ item[1] for item in li ]

def _unpickleTuple(node):
    return tuple(_unpickleList(node))

def _unpickleDict(node):
    dd = dict()
    for name, element in _getElementChilds(node):
        if name != "item":
            raise XMLUnpicklingException()
        childList = _getElementChilds(element)
        if len(childList) != 2:
            raise XMLUnpicklingException()
        for name, element in childList:
            if name=="key":
                key = _xmlunpickle(element)
            elif name=="value":
                value = _xmlunpickle(element)
        dd[key]=value
    return dd

if __name__ == '__main__':
    xml = "<root module=\"GMT.messaging.messages\" name=\"Response\"><code name=\"str\">OK</code><data name=\"NoneType\"/><id name=\"str\">a82a7ff68bdb0ff7659ccc582962ee4e</id></root>"
    res = unpickle(xml)
    print res.id